"""This module provides the decision rule parameters data class."""
from dataclasses import dataclass, field
from typing import List


@dataclass
class RuleParameters(object):
    """
    Decision rule parameters.

    Attrtibutes:
        a (float): loss factor for the greater class erroneous assignment
        b (float): loss factor for the lower class erroneous assignment
        k (float): norm learning rate
        z0 (float): norm start value (in period number 0)
        r (List[float]): decreasing rates for the norm in periods n + 1
        g (List[float]): increasing rates for the norm in periods n + 1
        m (int): amount of critical class periods to stop the loop
        n (int): the number of the loop
        i (int): the number of the iteration in the loop
        e (List[float]): the history of `e` values
        z (List[float]): the history of `z` values
        class_numbers (List[int]): class numbers
    """

    a: float = 0.5
    b: float = 5
    k: float = 0.1
    z0: float = -5
    r: List[float] = field(default_factory=lambda: [0.1])
    g: List[float] = field(default_factory=lambda: [0.1])
    m: int = 2
    n: int = 0
    i: int = 0
    e: List[float] = field(default_factory=list)
    z: List[float] = field(default_factory=list)
    class_numbers = List[int]

    def __post_init__(self):
        """Initialize evaluated parameters."""
        self.e = [self.get_initial_e_value()]
        self.z = [self.z0]
        self.n = 0
        # print(self)

    def get_initial_e_value(self):
        """Get initial value for the `e` parameter."""
        return self.z0 * (self.b + 1) / (self.a + self.b)

    def evaluate(self, x_t: float, classes: List[int]):
        """Evaluate dynamic parameters: `e` and `z`."""
        self.e.append(self.evaluate_e(x_t > self.z[-1], self.k / len(classes)))
        if self.loop_stops(classes):
            self.z.append(self.evaluate_new_z(self.e[-2] > 0))
        else:
            self.z.append(self.evaluate_loop_z())
        # print(self)

    def evaluate_e(self, is_gt_z: bool, d: float) -> float:
        """Evaluate new `e` value."""
        if is_gt_z:
            return self.e[-1] - d * self.b
        return self.e[-1] + d * self.a

    def evaluate_new_z(self, e_is_gt_0: bool) -> float:
        """Evaluate new `z` value."""
        while len(self.r) <= len(self.z):
            self.r.append(self.r[-1])
        while len(self.g) <= len(self.z):
            self.g.append(self.g[-1])
        if e_is_gt_0:
            return self.z[-1] * (1 + self.r[len(self.z)])
        else:
            return self.z[-1] * (1 - self.g[len(self.z)])

    def evaluate_loop_z(self):
        """Evaluate `z` value within the loop."""
        return self.e[-1] * (self.a + self.b) / (self.b + 1)

    def loop_stops(self, classes: List[int]):
        """Determine whether the loop should stop."""
        if (self.e[-1] > 0) or (
            self.i >= self.m - 1
            and len(classes) >= self.m
            and all(c == self.class_numbers[0] for c in classes[-self.m :])
        ):
            self.n += 1
            self.i = 0
            return True
        self.i += 1
        return False


__all__ = ["RuleParameters"]
